package com.gitee.wsl.prism4j

internal object ToString {
    fun toString(grammar: Prism4j.Grammar): String {
        val builder = StringBuilder()
        toString(builder, CacheImpl(), grammar)
        return builder.toString()
    }

    fun toString(token: Prism4j.Token): String {
        val builder = StringBuilder()
        toString(builder, CacheImpl(), token)
        return builder.toString()
    }

    fun toString(pattern: Prism4j.Pattern): String {
        val builder = StringBuilder()
        toString(builder, CacheImpl(), pattern)
        return builder.toString()
    }

    private fun toString(builder: StringBuilder, cache: Cache, grammar: Prism4j.Grammar) {
        builder
            .append("Grammar{id=0x")
            .append(Integer.toHexString(System.identityHashCode(grammar)))
            .append(",name=\"")
            .append(grammar.name())
            .append('\"')

        if (cache.visited(grammar)) {
            builder.append(",[...]")
        } else {
            cache.markVisited(grammar)
            builder.append(",tokens=[")
            var first = true
            for (token in grammar.tokens()) {
                if (first) {
                    first = false
                } else {
                    builder.append(',')
                }
                toString(builder, cache, token)
            }
            builder.append(']')
        }

        builder.append('}')
    }

    private fun toString(builder: StringBuilder, cache: Cache, token: Prism4j.Token) {
        builder
            .append("Token{id=0x")
            .append(Integer.toHexString(System.identityHashCode(token)))
            .append(",name=\"")
            .append(token.name())
            .append('\"')

        if (cache.visited(token)) {
            builder.append(",[...]")
        } else {
            cache.markVisited(token)
            builder.append(",patterns=[")
            var first = true
            for (pattern in token.patterns()) {
                if (first) {
                    first = false
                } else {
                    builder.append(',')
                }
                toString(builder, cache, pattern)
            }
            builder.append(']')
        }
        builder.append('}')
    }

    private fun toString(builder: StringBuilder, cache: Cache, pattern: Prism4j.Pattern) {
        builder
            .append("Pattern{id=0x")
            .append(Integer.toHexString(System.identityHashCode(pattern)))

        if (cache.visited(pattern)) {
            builder.append(",[...]")
        } else {
            cache.markVisited(pattern)

            builder.append(",regex=\"").append(pattern.regex()).append('\"')

            if (pattern.lookbehind()) {
                builder.append(",lookbehind=true")
            }

            if (pattern.greedy()) {
                builder.append(",greedy=true")
            }

            if (pattern.alias() != null) {
                builder.append(",alias=\"").append(pattern.alias()).append('\"')
            }

            val inside = pattern.inside()
            if (inside != null) {
                builder.append(",inside=")
                toString(builder, cache, inside)
            }
        }

        builder.append('}')
    }

    private interface Cache {
        fun visited(o: Any): Boolean

        fun markVisited(o: Any)
    }

    private class CacheImpl : Cache {
        private val cache: MutableSet<Int?> = HashSet<Int?>(3)

        override fun visited(o: Any): Boolean {
            return cache.contains(key(o))
        }

        override fun markVisited(o: Any) {
            cache.add(key(o))
        }

        companion object {
            private fun key(o: Any): Int {
                return System.identityHashCode(o)
            }
        }
    }
}
